﻿//Ben Gotthold
// Joe Illuminati
// CISC 360
// 9/23/2013
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections;

namespace CISC_360_project
{
    public struct processState
    {
        public Process process;
        public int reg_A;
        public int reg_B;
        public int reg_Acc;
        public int reg_PC;
        public int reg_MAR;
        public int reg_MDR;
        public int reg_Temp;
        public int reg_IR;
        public int reg_CC;
        public int reg_AccD;
        public int reg_Ovf;
        public int reg_Ret;
        public int exeCode;
        public int exeValue;
        public int exe_Instr_0;
        public int exe_Instr_2;
        public int exe_Instr_3;
        public int store_i;
        public int store_j;
        public bool store_b;
        public bool fetchDone;
        public bool decodeDone;
        public bool exeDone;
        public int mulStallCount;
        public bool branchStall;
        public bool decodeStall;
        public bool mulStall;
        public bool incrementPC;
        public bool ready_to_cycle;
    }

    public struct cacheBlock
    {
        public int tag;
        public int data;
        public bool dirtyFlag;
        public bool fromSpatial;
    }
    public struct twoWayCache
    {
        public cacheBlock[] blocks;
    }


    public class Memory   
    {

        public cacheBlock[] oneWayCache;        // one way cache structure
        public twoWayCache[] twoWayCache;       // two way cache structure

        // counter for gui purposes 
        public int cacheHit = 0;
        public int cacheMiss = 0;
        public int memAccess = 0;
        public int fromSpacial = 0;



        /// <summary>
        /// where instructions should go
        /// should look like:
        ///     Text.at(0) = "add #$10"
	    ///     Text.at(1) = "and $10"
        ///     Text.at(2) = "nop"
	    ///     Text.at(3) = "lda #$10"
        /// </summary>
        /// 
        public List<Instruction> Text;
        public byte[][] binary_instr;
        public List<int> used_memory;
        public Stack<int> stack_frame;

        #region Context Switching variables
        public List<processState> processes;
        public int cycles_until_switch;
        public int current_process;
        public int current_cycle;
        #endregion

        /// <summary>
        /// main memory
        ///  Note that Main Memory (Stack[]) should handle segmentation faults
	    /// As in Accessing Stack[256] should fail, but not break your program.
        /// </summary>
        public int[] stack = new int[256];

        public Memory()
        {
            this.Text = new List<Instruction>();
            this.binary_instr = null;
            this.used_memory = new List<int>();
            this.stack_frame = new Stack<int>();
            this.twoWayCache = new twoWayCache[8];
            this.oneWayCache = new cacheBlock[16];

            // TO BE USED FOR CONTEXT SWITCHING
            processes = new List<processState>();
            cycles_until_switch = 1;
            current_process = 0;
            current_cycle = 0;

            //set two way cache tags to -1 because can not be zero to start
            for (int i = 0; i < 8; i++)
            {
                twoWayCache[i].blocks  = new cacheBlock[2];
                twoWayCache[i].blocks[0].tag = -1;
                twoWayCache[i].blocks[1].tag = -1;
            }

            //set one way cache tags to -1 because can not be zero to start
            for (int i = 0; i < 16; i++)
            {
                oneWayCache[i].tag = -1;
            }

        }



        public void flushCacheWithSave(bool oneWay)
        {
            if (oneWay) // one way cache
            {
                for (int i = 0; i < 16; i++)    // loop through cache
                {
                    if (oneWayCache[i].tag != -1)
                    {
                        stack[oneWayCache[i].tag] = oneWayCache[i].data; // write back to mem
                        oneWayCache[i].tag = -1;                // set tag back to default
                    }

                }
            }
            else            // two way cache
            {
               
                for (int i = 0; i < 8; i++) //loop through cache
                {
                    if (twoWayCache[i].blocks[0].tag != -1)     // cahce in block 0 writen to
                    {
                        stack[twoWayCache[i].blocks[0].tag] = twoWayCache[i].blocks[0].data;    //save to mem
                        twoWayCache[i].blocks[0].tag = -1;      // set tag to default
                    }

                    if (twoWayCache[i].blocks[1].tag != -1) // cache in block two writen to
                    {
                        stack[twoWayCache[i].blocks[1].tag] = twoWayCache[i].blocks[1].data; // save to mem
                        twoWayCache[i].blocks[1].tag = -1;  // save tag
                    }
              
                }
            }
        }


        public int[] getBlockFromMemory(int adress){
            int[] block = new int[4];
            for (int i = 0; i < 4; i++)
            {
                if (adress + i < 255)
                {
                    block[i] = stack[adress + i];
                }
            }
            return block;
        } // function to pass a mem block to cache

        /// <summary>
        /// loads a binary file into memory without using 
        /// context switching.
        /// </summary>
        /// <param name="binary_loc"></param>
        public void loadInstructions(String binary_loc)
        {
            byte[][] instructions = null;
            using (BinaryReader br = new BinaryReader(File.Open(binary_loc, FileMode.Open)))
            {
                int length = (int)br.BaseStream.Length;
                instructions = new byte[length/4][];
                for (int i = 0; i < length/4; i++)
                {
                    instructions[i] = br.ReadBytes(4);
                }

            }
            binary_instr = instructions;
        } // Splits the binary file into a list of binary instructions for ease

        #region cache logic

        public int getDataTwoWay(int memOffSet)
        {
            int mod = ((memOffSet + 1) % 8);                                              //convert to cache adress

            if (twoWayCache[mod].blocks[0].tag == memOffSet)                              // first block of set 
            {
                                                                                          //chace hit!
                cacheHit++;
                if (twoWayCache[mod].blocks[0].fromSpatial)                               // check if benifit was from spacial
                {
                    fromSpacial++;
                }

                return twoWayCache[mod].blocks[0].data;
            }
            else if (twoWayCache[mod].blocks[1].tag == memOffSet)                         // second block of set 
            {
                                                                                          // cache hit!
                cacheHit++;
                if (twoWayCache[mod].blocks[1].fromSpatial)                               // check if benifit was from spacial
                {
                    fromSpacial++;
                }

                return twoWayCache[mod].blocks[1].data;
            }
            else
            {
                                                                                          //miss change tag to memory adress put into cache
                cacheMiss++;
                memAccess++;

                int[] block = getBlockFromMemory(memOffSet);                              // get a block from mem 
                int returnvalue =0;

                if (twoWayCache[mod].blocks[0].tag  == -1)                                // first block is empty
                {
                    twoWayCache[mod].blocks[0].tag = memOffSet;                           // update tag
                    twoWayCache[mod].blocks[0].data = block[0];                           //load data
                    returnvalue = twoWayCache[mod].blocks[0].data;
                }
                else if (twoWayCache[mod].blocks[1].tag == -1)                            // second block is empty
                {
                    twoWayCache[mod].blocks[1].tag = memOffSet;                           // update tag
                    twoWayCache[mod].blocks[1].data = block[0];                           //load data
                    returnvalue = twoWayCache[mod].blocks[1].data;
                }

                else                                                                      // both blocks full replace randomly
                {
                    Random random = new Random();
                    int randomNumber = random.Next(0, 2);                                     // for random replacment stratigy
                    
                    if (twoWayCache[mod].blocks[randomNumber].dirtyFlag)                  //check if cache is dirty
                    {
                        stack[memOffSet] = twoWayCache[mod].blocks[randomNumber].data;    // write dirty cache back to mem 
                        twoWayCache[mod].blocks[randomNumber].dirtyFlag = false;          // reset flag
                    }
                    
                    twoWayCache[mod].blocks[randomNumber].tag = memOffSet;                // update tag
                    twoWayCache[mod].blocks[randomNumber].data = stack[memOffSet];        //load data
                    returnvalue = twoWayCache[mod].blocks[randomNumber].data;
                }

                for (int i = 1; i < 4; i++)
                {
                    int modi = ((memOffSet + 1 + i) % 8);
                    if (twoWayCache[modi].blocks[0].tag == -1 && block[i] != 0)
                    {
                        twoWayCache[modi].blocks[0].tag = memOffSet + i;                 //change tag
                        twoWayCache[modi].blocks[0].data = block[i];                     // load data 
                        twoWayCache[modi].blocks[0].fromSpatial = true;                  // loaded from spacial
                    }
                    else if (twoWayCache[modi].blocks[1].tag == -1 && block[i] != 0)
                    {
                        twoWayCache[modi].blocks[1].tag = memOffSet + i;                 //change tag
                        twoWayCache[modi].blocks[1].data = block[i];                     // load data 
                        twoWayCache[modi].blocks[1].fromSpatial = true;                  // loaded from spacial
                    }
                }



                return returnvalue;
            }
        }

        public void writeDataTwoWay(int memOffSet, int data)
        {
            int mod = ((memOffSet + 1) % 8);

            if (twoWayCache[mod].blocks[0].tag == memOffSet)
            {
                //chace hit!
                cacheHit++; 
                twoWayCache[mod].blocks[0].data = data;   // write to cache
                twoWayCache[mod].blocks[0].dirtyFlag = true;      // dirty flag
            }
            else if (twoWayCache[mod].blocks[1].tag == memOffSet)
            {
                //chace hit!
                cacheHit++;
                twoWayCache[mod].blocks[1].data = data;   // write to cache
                twoWayCache[mod].blocks[1].dirtyFlag = true;       //dirty flag
            }

            else
            {

                //miss update in memory only
                memAccess++;
                cacheMiss++;
                stack[memOffSet] = data;      // not found write to memory only
            }
        }

        public int getDataOneWay(int memOffSet){
            // get the memory offset mod 16 add 1 to get rid of zero case, 15 becomes 0 
            int mod = ((memOffSet + 1) % 16);                                   // for the values position in cache, block 1
            
          

            if (oneWayCache[mod].tag == memOffSet)                              //chace hit!
            {
                cacheHit++;

                if (oneWayCache[mod].fromSpatial)                               // check if benifit was from spacial
                {
                    fromSpacial++;
                }
                return oneWayCache[mod].data;                                   //return data found in cache
            }
            else                                                                //miss change tag to memory adress put into cache
            {
                cacheMiss++;        
                memAccess++;
                int[] block = getBlockFromMemory(memOffSet);

                if (oneWayCache[mod].dirtyFlag)                                 //check if dirty
                {
                    stack[memOffSet] = oneWayCache[mod].data;                   //write dirty cache to mem
                    oneWayCache[mod].dirtyFlag = false;                         //reset  dirty flag
                }
                oneWayCache[mod].tag = memOffSet;                               //change tag
                oneWayCache[mod].data = block[0];                               // load data
 

                // for spacial localality, try to load more data into the cache on a memory axcess

                for (int i = 1; i < 4; i++)
                {
                    int modi = ((memOffSet + 1 + i) % 16);
                    if (oneWayCache[modi].tag == -1 && block[i] != 0)
                    {
                        oneWayCache[modi].tag = memOffSet + i;                  //change tag
                        oneWayCache[modi].data = block[i];                      // load data 
                        oneWayCache[modi].fromSpatial = true;
                    }
                }



                return oneWayCache[mod].data;     
            }
        }

        public void writeDataOneWay(int memOffSet,int data)
        {
            int mod = ((memOffSet + 1) % 16);

            if (oneWayCache[mod].tag == memOffSet)
            {
                //chace hit!
                cacheHit++;
                 oneWayCache[mod].data = data;    // write data to cache
                 oneWayCache[mod].dirtyFlag = true;   //dirty flag
            }
            else
            {
                
                //miss update in memory only
                memAccess++;
                cacheMiss++;
                stack[memOffSet] = data;    // not found write to memory only
            }

        }

        #endregion

        #region context switching logic

        /// <summary>
        /// Loads a binary file into memory with context switching.
        /// </summary>
        /// <param name="binary_loc"></param>
        /// <param name="instructions"></param>
        /// <param name="cpu"></param>
        public void loadProcess(String binary_loc, List<Instruction> instructions, CPU cpu)
        {
            processState ps = new processState();

            byte[][] bin = null;
            using (BinaryReader br = new BinaryReader(File.Open(binary_loc, FileMode.Open)))
            {
                int length = (int)br.BaseStream.Length;
                bin = new byte[length / 4][];
                for (int i = 0; i < length / 4; i++)
                {
                    bin[i] = br.ReadBytes(4);
                }

            }

            ps.process = new Process(instructions, bin);
            ps.reg_A = 0;
            ps.reg_B = 0;
            ps.reg_Acc = 0;
            ps.reg_PC = 1;
            ps.reg_IR = 0;
            ps.reg_MAR = 0;
            ps.reg_MDR = 0;
            ps.reg_Ovf = 0;
            ps.reg_Temp = 0;
            ps.reg_Ret = 0;
            ps.reg_AccD = 0;
            ps.reg_CC = 0;
            ps.exeCode = -1;
            ps.exeValue = 0;
            ps.exe_Instr_0 = -1;
            ps.exe_Instr_2 = -1;
            ps.exe_Instr_3 = -1;

            ps.store_i = 0;
            ps.store_j = 0;
            ps.store_b = false;

            ps.fetchDone = false;
            ps.decodeDone = false;
            ps.exeDone= false;
     
            ps.mulStallCount = 0;
            ps.branchStall = false;
            ps.decodeStall = false;
            ps.mulStall = false;

            ps.incrementPC = true;
            ps.ready_to_cycle = false;

            processes.Add(ps);
        }
        /// <summary>
        /// Saves the value of the registers in the CPU to the
        /// processState.
        /// </summary>
        /// <param name="state"></param>
        /**
        public void saveProcessState(processState state)
        {
            state.reg_A          = state.process.CPU_RUNNING_ON.A.Data;
            state.reg_B          = state.process.CPU_RUNNING_ON.B.Data;
            state.reg_Acc        = state.process.CPU_RUNNING_ON.Acc.Data;
            state.reg_PC         = state.process.CPU_RUNNING_ON.PC.Data;
            state.reg_MAR        = state.process.CPU_RUNNING_ON.MAR.Data;
            state.reg_MDR        = state.process.CPU_RUNNING_ON.MDR.Data;
            state.reg_Temp       = state.process.CPU_RUNNING_ON.Temp.Data;
            state.reg_IR         = state.process.CPU_RUNNING_ON.IR.Data;
            state.reg_CC         = state.process.CPU_RUNNING_ON.CC.Data;
            state.reg_AccD       = state.process.CPU_RUNNING_ON.AccD.Data;
            state.reg_Ovf        = state.process.CPU_RUNNING_ON.Ovf.Data;
            state.reg_Ret        = state.process.CPU_RUNNING_ON.Ret.Data;
            state.exeCode        = state.process.CPU_RUNNING_ON.exeCode;      
            state.exeValue       = state.process.CPU_RUNNING_ON.exeValue;     
            state.exe_Instr_0    = state.process.CPU_RUNNING_ON.exe_Instr_0;  
            state.exe_Instr_2    = state.process.CPU_RUNNING_ON.exe_Instr_2; 
            state.exe_Instr_3    = state.process.CPU_RUNNING_ON.exe_Instr_3;
            state.store_i        = state.process.CPU_RUNNING_ON.store_i;      
            state.store_j        = state.process.CPU_RUNNING_ON.store_j;      
            state.store_b        = state.process.CPU_RUNNING_ON.store_b;      
            state.fetchDone      = state.process.CPU_RUNNING_ON.fetchDone;    
            state.decodeDone     = state.process.CPU_RUNNING_ON.decodeDone;   
            state.exeDone        = state.process.CPU_RUNNING_ON.exeDone;      
            state.mulStallCount  = state.process.CPU_RUNNING_ON.mulStallCount;
            state.branchStall    = state.process.CPU_RUNNING_ON.branchStall;  
            state.decodeStall    = state.process.CPU_RUNNING_ON.decodeStall; 
            state.mulStall       = state.process.CPU_RUNNING_ON.mulStall;
            state.incrementPC    = state.process.CPU_RUNNING_ON.incrementPC;
            state.ready_to_cycle = state.process.CPU_RUNNING_ON.ready_to_cycle;
        }          
        **/
        
        /// <summary>
        /// Load the values of the state register values into the 
        /// CPUs registers.
        /// </summary>
        /// <param name="state"></param>
        /**
        public void loadProcesssState(processState state)
        {
            state.process.CPU_RUNNING_ON.A.Data         = state.reg_A;
            state.process.CPU_RUNNING_ON.B.Data         = state.reg_B;  
            state.process.CPU_RUNNING_ON.Acc.Data       = state.reg_Acc; 
            state.process.CPU_RUNNING_ON.PC.Data        = state.reg_PC;  
            state.process.CPU_RUNNING_ON.MAR.Data       = state.reg_MAR; 
            state.process.CPU_RUNNING_ON.MDR.Data       = state.reg_MDR; 
            state.process.CPU_RUNNING_ON.Temp.Data      = state.reg_Temp;
            state.process.CPU_RUNNING_ON.IR.Data        = state.reg_IR;  
            state.process.CPU_RUNNING_ON.CC.Data        = state.reg_CC;  
            state.process.CPU_RUNNING_ON.AccD.Data      = state.reg_AccD;
            state.process.CPU_RUNNING_ON.Ovf.Data       = state.reg_Ovf;
            state.process.CPU_RUNNING_ON.Ret.Data       = state.reg_Ret;
            state.process.CPU_RUNNING_ON.exeCode        = state.exeCode;
            state.process.CPU_RUNNING_ON.exeValue       = state.exeValue;
            state.process.CPU_RUNNING_ON.exe_Instr_0    = state.exe_Instr_0;
            state.process.CPU_RUNNING_ON.exe_Instr_2    = state.exe_Instr_2;
            state.process.CPU_RUNNING_ON.exe_Instr_3    = state.exe_Instr_3;
            state.process.CPU_RUNNING_ON.store_i        = state.store_i;
            state.process.CPU_RUNNING_ON.store_j        = state.store_j;
            state.process.CPU_RUNNING_ON.store_b        = state.store_b;   
            state.process.CPU_RUNNING_ON.fetchDone      = state.fetchDone;
            state.process.CPU_RUNNING_ON.decodeDone     = state.decodeDone;
            state.process.CPU_RUNNING_ON.exeDone        = state.exeDone;
            state.process.CPU_RUNNING_ON.mulStallCount  = state.mulStallCount;
            state.process.CPU_RUNNING_ON.branchStall    = state.branchStall;
            state.process.CPU_RUNNING_ON.decodeStall    = state.decodeStall;
            state.process.CPU_RUNNING_ON.mulStall       = state.mulStall;
            state.process.CPU_RUNNING_ON.incrementPC    = state.incrementPC;
            state.process.CPU_RUNNING_ON.ready_to_cycle = state.ready_to_cycle;
        }
         **/
        #endregion

    }
}
